package edu.northwestern.cbits.purple_robot_manager; import android.app.IntentService; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.BatteryManager; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import android.util.LongSparseArray; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.MessageApi; import com.google.android.gms.wearable.MessageEvent; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; public class SensorService extends IntentService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private static final String PATH_REQUEST_DATA = "/purple-robot/request-data"; private static final String PATH_SEND_CONFIG = "/purple-robot/send-config"; public static final String SENSOR_MAXIMUM_RANGE = "MAXIMUM_RANGE"; public static final String SENSOR_NAME = "NAME"; public static final String SENSOR_POWER = "POWER"; public static final String SENSOR_TYPE = "TYPE"; public static final String SENSOR_VENDOR = "VENDOR"; public static final String SENSOR_VERSION = "VERSION"; public static final String SENSOR_RESOLUTION = "RESOLUTION"; public static final String BUNDLE_SENSOR = "SENSOR"; public static final String SENSOR_ACCURACY = "ACCURACY"; public static final String EVENT_TIMESTAMP = "EVENT_TIMESTAMP"; public static final String SENSOR_TIMESTAMP = "SENSOR_TIMESTAMP"; protected static final String BUNDLE_PROBE = "PROBE"; protected static final String BUNDLE_TIMESTAMP = "TIMESTAMP"; private static final String BUNDLE_SOURCE = "SOURCE"; private static final String ACCELEROMETER_ENABLED = "SensorService.ACCELEROMETER_ENABLED"; private static final boolean ACCELEROMETER_DEFAULT_ENABLED = true; private static final String ACCELEROMETER_FREQUENCY = "SensorService.ACCELEROMETER_FREQUENCY"; private static final String LIGHT_METER_ENABLED = "SensorService.LIGHT_METER_ENABLED"; private static final boolean LIGHT_METER_DEFAULT_ENABLED = false; private static final String LIGHT_METER_FREQUENCY = "SensorService.LIGHT_METER_FREQUENCY"; private static final String MAGNETOMETER_ENABLED = "SensorService.MAGNETOMETER_ENABLED"; private static final boolean MAGNETOMETER_DEFAULT_ENABLED = false; private static final String MAGNETOMETER_FREQUENCY = "SensorService.MAGNETOMETER_FREQUENCY"; private static final String GYROSCOPE_ENABLED = "SensorService.GYROSCOPE_ENABLED"; private static final boolean GYROSCOPE_DEFAULT_ENABLED = false; private static final String GYROSCOPE_FREQUENCY = "SensorService.GYROSCOPE_FREQUENCY"; private static final String HEART_METER_ENABLED = "SensorService.HEART_METER_ENABLED"; private static final boolean HEART_METER_DEFAULT_ENABLED = false; private static final String HEART_METER_FREQUENCY = "SensorService.HEART_METER_FREQUENCY"; private static final String LIVEWELL_COUNTS_ENABLED = "SensorService.LIVEWELL_COUNTS_ENABLED"; private static final boolean LIVEWELL_COUNTS_DEFAULT_ENABLED = false; private static final String LIVEWELL_COUNTS_BIN_SIZE = "SensorService.LIVEWELL_COUNTS_BIN_SIZE"; private static final int LIVEWELL_COUNTS_DEFAULT_BIN_SIZE = 60; private static final int DEFAULT_FREQUENCY = SensorManager.SENSOR_DELAY_NORMAL; private static final String URI_READING_PREFIX = "/purple-robot-reading"; private static final String BATTERY_LEVEL = "BATTERY_LEVEL"; private static final String BATTERY_SCALE = "BATTERY_SCALE"; private static final String BATTERY_CHARGING = "BATTERY_CHARGING"; private static final String BATTERY_CHARGE_SOURCE = "BATTERY_CHARGE_SOURCE"; private static final String URI_CRASH_REPORT = "/purple-robot-crash"; private static SensorEventListener heartListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) { if (SensorService._heartEnabled) HeartHandler.handleSensorEvent(sensorEvent); } public void onAccuracyChanged(Sensor sensor, int i) { } }; private static SensorEventListener lightListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) { if (SensorService._lightEnabled) LightHandler.handleSensorEvent(sensorEvent); } public void onAccuracyChanged(Sensor sensor, int i) { } }; private static SensorEventListener magnetometerListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) { if (SensorService._magnetEnabled) MagneticFieldHandler.handleSensorEvent(sensorEvent); } public void onAccuracyChanged(Sensor sensor, int i) { } }; private static SensorEventListener gyroscopeListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) { if (SensorService._gyroEnabled) GyroscopeHandler.handleSensorEvent(sensorEvent); } public void onAccuracyChanged(Sensor sensor, int i) { } }; private static SensorEventListener accelerometerListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) { if (SensorService._accelEnabled) AccelerometerHandler.handleSensorEvent(sensorEvent); if (SensorService._livewellEnabled) LivewellActivityCountHandler.handleSensorEvent(sensorEvent, SensorService._livewellBinSize); } public void onAccuracyChanged(Sensor sensor, int i) { } }; private static BroadcastReceiver batteryListener = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { SensorService._batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); SensorService._batteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); SensorService._batteryCharging = (status == BatteryManager.BATTERY_STATUS_CHARGING); int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); if (plugged == BatteryManager.BATTERY_PLUGGED_USB) SensorService._batteryPlug = "usb"; else if (plugged == BatteryManager.BATTERY_PLUGGED_AC) SensorService._batteryPlug = "wall"; else if (plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS) SensorService._batteryPlug = "wireless"; else SensorService._batteryPlug = "none/unknown"; } }; private static GoogleApiClient _apiClient = null; private final static LongSparseArray<DataMap> _payloads = new LongSparseArray<>(); private final static ArrayList<DataMap> _crashPayloads = new ArrayList<>(); private static boolean _isTransmitting = false; private static boolean _accelEnabled = false; private static boolean _magnetEnabled = false; private static boolean _livewellEnabled = false; private static boolean _gyroEnabled = false; private static boolean _lightEnabled = false; private static boolean _heartEnabled = false; private static boolean _accelRegistered = false; private static boolean _gyroRegistered = false; private static boolean _magnetRegistered = false; private static boolean _heartRegistered = false; private static boolean _lightRegistered = false; private static int _livewellBinSize = 60; private static int _lastAccelRate = -1; private static int _lastGyroRate = -1; private static int _lastMagnetRate = -1; private static int _lastLightRate = -1; private static int _lastHeartRate = -1; private static boolean _batteryInited = false; private static int _batteryLevel = -1; private static int _batteryScale = -1; private static boolean _batteryCharging = false; private static String _batteryPlug = "unknown"; private static long _lastBatteryLog = 0; public SensorService() { super("SensorService"); } public void onCreate() { super.onCreate(); if (SensorService._apiClient == null) { GoogleApiClient.Builder builder = new GoogleApiClient.Builder(this); builder.addApi(Wearable.API); builder.addConnectionCallbacks(this); builder.addOnConnectionFailedListener(this); SensorService._apiClient = builder.build(); SensorService._apiClient.connect(); } File f = new File(this.getFilesDir(), "crash.acra"); if (f.exists()) { try { FileInputStream in = new FileInputStream(f); ByteArrayOutputStream out = new ByteArrayOutputStream(); int read = 0; byte[] buffer = new byte[4096]; while ((read = in.read(buffer, 0, buffer.length)) != -1) { out.write(buffer, 0, read); } in.close(); DataMap map = DataMap.fromByteArray(out.toByteArray()); SensorService._crashPayloads.add(map); f.delete(); } catch (IOException e) { e.printStackTrace(); } } } private void setupGyroscope(SensorManager sensors) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SensorService._gyroEnabled = prefs.getBoolean(SensorService.GYROSCOPE_ENABLED, SensorService.GYROSCOPE_DEFAULT_ENABLED); int rate = prefs.getInt(SensorService.GYROSCOPE_FREQUENCY, SensorService.DEFAULT_FREQUENCY); if (rate != SensorService._lastGyroRate) { try { sensors.unregisterListener(SensorService.gyroscopeListener, sensors.getDefaultSensor(Sensor.TYPE_GYROSCOPE)); } catch (Exception e) { e.printStackTrace(); } SensorService._lastGyroRate = rate; SensorService._gyroRegistered = false; } if (SensorService._gyroEnabled && SensorService._gyroRegistered == false) { sensors.registerListener(SensorService.gyroscopeListener, sensors.getDefaultSensor(Sensor.TYPE_GYROSCOPE), rate); SensorService._gyroRegistered = false; } else if (SensorService._gyroEnabled == false) { try { sensors.unregisterListener(SensorService.gyroscopeListener, sensors.getDefaultSensor(Sensor.TYPE_GYROSCOPE)); } catch (Exception e) { e.printStackTrace(); } SensorService._gyroRegistered = false; } } private void setupAccelerometer(SensorManager sensors) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()); SensorService._accelEnabled = prefs.getBoolean(SensorService.ACCELEROMETER_ENABLED, SensorService.ACCELEROMETER_DEFAULT_ENABLED); SensorService._livewellEnabled = prefs.getBoolean(SensorService.LIVEWELL_COUNTS_ENABLED, SensorService.LIVEWELL_COUNTS_DEFAULT_ENABLED); SensorService._livewellBinSize = prefs.getInt(SensorService.LIVEWELL_COUNTS_BIN_SIZE, SensorService.LIVEWELL_COUNTS_DEFAULT_BIN_SIZE); int rate = prefs.getInt(SensorService.ACCELEROMETER_FREQUENCY, SensorService.DEFAULT_FREQUENCY); if (rate != SensorService._lastAccelRate) { try { sensors.unregisterListener(SensorService.accelerometerListener, sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)); } catch (Exception e) { e.printStackTrace(); } SensorService._lastAccelRate = rate; SensorService._accelRegistered = false; } if ((SensorService._accelEnabled || SensorService._livewellEnabled) && SensorService._accelRegistered == false) sensors.registerListener(SensorService.accelerometerListener, sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), rate); else if (SensorService._accelEnabled == false && SensorService._livewellEnabled == false && SensorService._accelRegistered) { try { sensors.unregisterListener(SensorService.accelerometerListener, sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)); } catch (Exception e) { e.printStackTrace(); } SensorService._accelRegistered = false; } } private void setupMagnetometer(SensorManager sensors) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SensorService._magnetEnabled = prefs.getBoolean(SensorService.MAGNETOMETER_ENABLED, SensorService.MAGNETOMETER_DEFAULT_ENABLED); int rate = prefs.getInt(SensorService.MAGNETOMETER_FREQUENCY, SensorService.DEFAULT_FREQUENCY); if (rate != SensorService._lastMagnetRate) { try { sensors.unregisterListener(SensorService.magnetometerListener, sensors.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)); } catch (Exception e) { e.printStackTrace(); } SensorService._lastMagnetRate = rate; SensorService._magnetRegistered = false; } if (SensorService._magnetEnabled && SensorService._magnetRegistered == false) { sensors.registerListener(SensorService.magnetometerListener, sensors.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), rate); SensorService._magnetRegistered = false; } else if (SensorService._magnetEnabled == false) { try { sensors.unregisterListener(SensorService.magnetometerListener, sensors.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)); } catch (Exception e) { e.printStackTrace(); } SensorService._magnetRegistered = false; } } private void setupLightMeter(SensorManager sensors) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SensorService._lightEnabled = prefs.getBoolean(SensorService.LIGHT_METER_ENABLED, SensorService.LIGHT_METER_DEFAULT_ENABLED); int rate = prefs.getInt(SensorService.LIGHT_METER_FREQUENCY, SensorService.DEFAULT_FREQUENCY); if (rate != SensorService._lastLightRate) { try { sensors.unregisterListener(SensorService.lightListener, sensors.getDefaultSensor(Sensor.TYPE_LIGHT)); } catch (Exception e) { e.printStackTrace(); } SensorService._lastLightRate = rate; SensorService._lightRegistered = false; } if (SensorService._lightEnabled && SensorService._lightRegistered == false) { sensors.registerListener(SensorService.lightListener, sensors.getDefaultSensor(Sensor.TYPE_LIGHT), rate); SensorService._lightRegistered = false; } else if (SensorService._lightEnabled == false) { try { sensors.unregisterListener(SensorService.lightListener, sensors.getDefaultSensor(Sensor.TYPE_LIGHT)); } catch (Exception e) { e.printStackTrace(); } SensorService._lightRegistered = false; } } private void setupHeartMeter(SensorManager sensors) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SensorService._heartEnabled = prefs.getBoolean(SensorService.HEART_METER_ENABLED, SensorService.HEART_METER_DEFAULT_ENABLED); int rate = prefs.getInt(SensorService.HEART_METER_FREQUENCY, SensorService.DEFAULT_FREQUENCY); if (rate != SensorService._lastHeartRate) { try { sensors.unregisterListener(SensorService.heartListener, sensors.getDefaultSensor(Sensor.TYPE_HEART_RATE)); } catch (Exception e) { e.printStackTrace(); } SensorService._lastHeartRate = rate; SensorService._heartRegistered = false; } if (SensorService._heartEnabled && SensorService._heartRegistered == false) { sensors.registerListener(SensorService.heartListener, sensors.getDefaultSensor(Sensor.TYPE_HEART_RATE), rate); SensorService._heartRegistered = false; } else if (SensorService._heartEnabled == false) { try { sensors.unregisterListener(SensorService.heartListener, sensors.getDefaultSensor(Sensor.TYPE_HEART_RATE)); } catch (Exception e) { e.printStackTrace(); } SensorService._heartRegistered = false; } } protected void onHandleIntent(Intent intent) { SensorManager sensors = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE); this.setupAccelerometer(sensors); this.setupGyroscope(sensors); this.setupMagnetometer(sensors); this.setupLightMeter(sensors); this.setupHeartMeter(sensors); if (SensorService._batteryInited == false) { IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); this.registerReceiver(SensorService.batteryListener, filter); SensorService._batteryInited = true; } } public static int currentBatteryLevel() { return SensorService._batteryLevel; } public static int pendingPayloadsCount() { return SensorService._payloads.size(); } public static void transmitData(String source, DataMap data) { synchronized (SensorService._payloads) { while (SensorService._payloads.size() > 512) SensorService._payloads.removeAt(0); data.putString(SensorService.BUNDLE_SOURCE, source); SensorService._payloads.append(System.currentTimeMillis(), data); } long now = System.currentTimeMillis(); if (now - SensorService._lastBatteryLog > 60000) { DataMap batteryData = new DataMap(); batteryData.putDouble(SensorService.BUNDLE_TIMESTAMP, now / 1000); batteryData.putString(SensorService.BUNDLE_PROBE, "edu.northwestern.cbits.purple_robot_manager.WearBatteryProbe"); batteryData.putInt(SensorService.BATTERY_LEVEL, SensorService._batteryLevel); batteryData.putInt(SensorService.BATTERY_SCALE, SensorService._batteryScale); batteryData.putBoolean(SensorService.BATTERY_CHARGING, SensorService._batteryCharging); batteryData.putString(SensorService.BATTERY_CHARGE_SOURCE, SensorService._batteryPlug); SensorService._lastBatteryLog = now; SensorService.transmitData("battery", batteryData); } } @Override public void onConnected(Bundle bundle) { final SensorService me = this; Wearable.MessageApi.addListener(SensorService._apiClient, new MessageApi.MessageListener() { public void onMessageReceived(MessageEvent event) { Log.e("PW", "GOT PATH: " + event.getPath()); if (SensorService.PATH_REQUEST_DATA.equals(event.getPath())) { if (SensorService._isTransmitting == false) { SensorService._isTransmitting = true; long now = System.currentTimeMillis(); ArrayList<Long> transmitted = new ArrayList<>(); synchronized (SensorService._payloads) { for (int i = 0; i < SensorService._payloads.size(); i++) { Long timestamp = SensorService._payloads.keyAt(i); if (timestamp < now) { if (SensorService._apiClient.isConnected()) { DataMap map = SensorService._payloads.valueAt(i); PutDataMapRequest putDataMapReq = PutDataMapRequest.create(SensorService.URI_READING_PREFIX + "/" + map.getString(SensorService.BUNDLE_SOURCE) + "/" + System.currentTimeMillis()); putDataMapReq.getDataMap().putAll(map); PutDataRequest putDataReq = putDataMapReq.asPutDataRequest(); Wearable.DataApi.putDataItem(SensorService._apiClient, putDataReq); Log.e("PW", "SENT PAYLOAD " + timestamp); transmitted.add(timestamp); } } } for (Long timestamp : transmitted) { SensorService._payloads.remove(timestamp); } } synchronized (SensorService._crashPayloads) { ArrayList<Integer> sent = new ArrayList<>(); for (int i = 0; i < SensorService._crashPayloads.size(); i++) { if (SensorService._apiClient.isConnected()) { DataMap crashReport = SensorService._crashPayloads.get(i); PutDataMapRequest putDataMapReq = PutDataMapRequest.create(SensorService.URI_CRASH_REPORT + "/" + System.currentTimeMillis()); putDataMapReq.getDataMap().putAll(crashReport); PutDataRequest putDataReq = putDataMapReq.asPutDataRequest(); Wearable.DataApi.putDataItem(SensorService._apiClient, putDataReq); Log.e("PW", "SENT CRASH " + i); sent.add(i); } } Collections.sort(sent); Collections.reverse(sent); for (Integer i : sent) { SensorService._crashPayloads.remove(i.intValue()); } } SensorService._isTransmitting = false; } } else if (SensorService.PATH_SEND_CONFIG.equals(event.getPath())) { byte[] config = event.getData(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me); SharedPreferences.Editor e = prefs.edit(); e.putBoolean(SensorService.ACCELEROMETER_ENABLED, config[0] > 0x00); e.putBoolean(SensorService.GYROSCOPE_ENABLED, config[2] > 0x00); e.putBoolean(SensorService.MAGNETOMETER_ENABLED, config[4] > 0x00); e.putBoolean(SensorService.LIGHT_METER_ENABLED, config[6] > 0x00); e.putBoolean(SensorService.HEART_METER_ENABLED, config[8] > 0x00); if (config.length > 10) e.putBoolean(SensorService.LIVEWELL_COUNTS_ENABLED, config[10] > 0x00); e.putInt(SensorService.ACCELEROMETER_FREQUENCY, (int) config[1]); e.putInt(SensorService.GYROSCOPE_FREQUENCY, (int) config[3]); e.putInt(SensorService.MAGNETOMETER_FREQUENCY, (int) config[5]); e.putInt(SensorService.LIGHT_METER_FREQUENCY, (int) config[7]); e.putInt(SensorService.HEART_METER_FREQUENCY, (int) config[9]); if (config.length > 11) e.putInt(SensorService.LIVEWELL_COUNTS_BIN_SIZE, (int) config[11]); e.commit(); } } }); } public static String bytesToHex(byte[] bytes) { final char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } @Override public void onConnectionSuspended(int i) { Log.e("PW", "CONNECTION SUSPENDED " + i); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.e("PW", "CONNECT FAILED: " + connectionResult.toString()); final SensorService me = this; Runnable r = new Runnable() { public void run() { try { Thread.sleep(2500); } catch (InterruptedException e) { e.printStackTrace(); } SensorService._apiClient.connect(); } }; Thread t = new Thread(r); t.start(); } }